home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / pyshared / DistUpgrade / DistUpgradeView.py < prev    next >
Encoding:
Python Source  |  2009-04-27  |  11.7 KB  |  325 lines

  1. # DistUpgradeView.py 
  2. #  
  3. #  Copyright (c) 2004,2005 Canonical
  4. #  
  5. #  Author: Michael Vogt <michael.vogt@ubuntu.com>
  6. #  This program is free software; you can redistribute it and/or 
  7. #  modify it under the terms of the GNU General Public License as 
  8. #  published by the Free Software Foundation; either version 2 of the
  9. #  License, or (at your option) any later version.
  10. #  This program is distributed in the hope that it will be useful,
  11. #  but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. #  GNU General Public License for more details.
  14. #  You should have received a copy of the GNU General Public License
  15. #  along with this program; if not, write to the Free Software
  16. #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  17. #  USA
  18.  
  19. from DistUpgradeGettext import gettext as _
  20. from DistUpgradeGettext import ngettext
  21. import subprocess
  22. from subprocess import Popen, PIPE
  23. import apt
  24. import os
  25. import apt_pkg 
  26. import signal
  27. import glob
  28.  
  29. from DistUpgradeAufs import doAufsChroot, doAufsChrootRsync
  30. from DistUpgradeApport import *
  31.  
  32.  
  33. def FuzzyTimeToStr(sec):
  34.   " return the time a bit fuzzy (no seconds if time > 60 secs "
  35.   #print "FuzzyTimeToStr: ", sec
  36.   sec = int(sec)
  37.  
  38.   days = sec/(60*60*24)
  39.   hours = sec/(60*60) % 24
  40.   minutes = (sec/60) % 60
  41.   seconds = sec % 60
  42.   # 0 seonds remaining looks wrong and its "fuzzy" anyway
  43.   if seconds == 0:
  44.     seconds = 1
  45.  
  46.   # string map to make the re-ordering possible
  47.   map = { "str_days" : "",
  48.           "str_hours" : "",
  49.           "str_minutes" : "",
  50.           "str_seconds" : ""
  51.         }
  52.   
  53.   # get the fragments, this is not ideal i18n wise, but its
  54.   # difficult to do it differently
  55.   if days > 0:
  56.     map["str_days"] = ngettext("%li day","%li days", days) % days
  57.   if hours > 0:
  58.     map["str_hours"] = ngettext("%li hour","%li hours", hours) % hours
  59.   if minutes > 0:
  60.     map["str_minutes"] = ngettext("%li minute","%li minutes", minutes) % minutes
  61.   map["str_seconds"] = ngettext("%li second","%li seconds", seconds) % seconds
  62.  
  63.   # now assemble the string
  64.   if days > 0:
  65.     # TRANSLATORS: you can alter the ordering of the remaining time
  66.     # information here if you shuffle %(str_days)s %(str_hours)s %(str_minutes)s
  67.     # around. Make sure to keep all '$(str_*)s' in the translated string
  68.     # and do NOT change anything appart from the ordering.
  69.     #
  70.     # %(str_hours)s will be either "1 hour" or "2 hours" depending on the
  71.     # plural form
  72.     # 
  73.     # Note: most western languages will not need to change this
  74.     return _("%(str_days)s %(str_hours)s") % map
  75.   # display no minutes for time > 3h, see LP: #144455
  76.   elif hours > 3:
  77.     return map["str_hours"]
  78.   # when we are near the end, become more precise again
  79.   elif hours > 0:
  80.     # TRANSLATORS: you can alter the ordering of the remaining time
  81.     # information here if you shuffle %(str_hours)s %(str_minutes)s
  82.     # around. Make sure to keep all '$(str_*)s' in the translated string
  83.     # and do NOT change anything appart from the ordering.
  84.     #
  85.     # %(str_hours)s will be either "1 hour" or "2 hours" depending on the
  86.     # plural form
  87.     # 
  88.     # Note: most western languages will not need to change this
  89.     return _("%(str_hours)s %(str_minutes)s") % map
  90.   elif minutes > 0:
  91.     return map["str_minutes"]
  92.   return map["str_seconds"]
  93.  
  94.  
  95. class FetchProgress(apt.progress.FetchProgress):
  96.   def __init__(self):
  97.     #print "init FetchProgress in DistUpgradeView"
  98.     apt.progress.FetchProgress.__init__(self)
  99.     self.est_speed = 0
  100.   def pulse(self):
  101.     apt.progress.FetchProgress.pulse(self)
  102.     if self.currentCPS > self.est_speed:
  103.       self.est_speed = (self.est_speed+self.currentCPS)/2.0
  104.     return True
  105.   def estimatedDownloadTime(self, requiredDownload):
  106.     """ get the estimated download time """
  107.     if self.est_speed == 0:
  108.       timeModem = requiredDownload/(56*1024/8)  # 56 kbit 
  109.       timeDSL = requiredDownload/(1024*1024/8)  # 1Mbit = 1024 kbit
  110.       s= _("This download will take about %s with a 1Mbit DSL connection "
  111.            "and about %s with a 56k modem.") % (FuzzyTimeToStr(timeDSL), FuzzyTimeToStr(timeModem))
  112.       return s
  113.     # if we have a estimated speed, use it
  114.     s = _("This download will take about %s with your connection. ") % FuzzyTimeToStr(requiredDownload/self.est_speed)
  115.     return s
  116.     
  117.  
  118.  
  119. class InstallProgress(apt.progress.InstallProgress):
  120.   """ Base class for InstallProgress that supports some fancy
  121.       stuff like apport integration
  122.   """
  123.   def __init__(self):
  124.     apt.progress.InstallProgress.__init__(self)
  125.     self.master_fd = None
  126.  
  127.   def run(self, pm):
  128.     pid = self.fork()
  129.     if pid == 0:
  130.       # check if we need to setup/enable the aufs chroot stuff
  131.       if "RELEASE_UPGRADE_USE_AUFS_CHROOT" in os.environ:
  132.         if not doAufsChroot(os.environ["RELEASE_UPGRADE_AUFS_RWDIR"],
  133.                             os.environ["RELEASE_UPGRADE_USE_AUFS_CHROOT"]):
  134.           print "ERROR: failed to setup aufs chroot overlay"
  135.           os._exit(1)
  136.       # child, ignore sigpipe, there are broken scripts out there
  137.       # like etckeeper (LP: #283642)
  138.       signal.signal(signal.SIGPIPE,signal.SIG_IGN) 
  139.       res = pm.DoInstall(self.writefd)
  140.       os._exit(res)
  141.     self.child_pid = pid
  142.     res = self.waitChild()
  143.     # check if we want to sync the changes back, *only* do that
  144.     # if res is positive
  145.     if (res == 0 and
  146.         "RELEASE_UPGRADE_RSYNC_AUFS_CHROOT" in os.environ):
  147.       logging.info("doing rsync commit of the update")
  148.       if not doAufsChrootRsync(os.environ["RELEASE_UPGRADE_USE_AUFS_CHROOT"]):
  149.         logging.error("FATAL ERROR: doAufsChrootRsync() returned FALSE")
  150.         return False
  151.     return res
  152.   
  153.   def error(self, pkg, errormsg):
  154.     " install error from a package "
  155.     apt.progress.InstallProgress.error(self, pkg, errormsg)
  156.     logging.error("got an error from dpkg for pkg: '%s': '%s'" % (pkg, errormsg))
  157.     if "/" in pkg:
  158.       pkg = os.path.basename(pkg)
  159.     if "_" in pkg:
  160.       pkg = pkg.split("_")[0]
  161.     # now run apport
  162.     apport_pkgfailure(pkg, errormsg)
  163.  
  164. class DumbTerminal(object):
  165.     def call(self, cmd, hidden=False):
  166.         " expects a command in the subprocess style (as a list) "
  167.         import subprocess
  168.         subprocess.call(cmd)
  169.  
  170.  
  171. (STEP_PREPARE,
  172.  STEP_MODIFY_SOURCES,
  173.  STEP_FETCH,
  174.  STEP_INSTALL,
  175.  STEP_CLEANUP,
  176.  STEP_REBOOT,
  177.  STEP_N) = range(1,8)
  178.  
  179. ( _("Preparing to upgrade"),
  180.   _("Getting new software channels"),
  181.   _("Getting new packages"),
  182.   _("Installing the upgrades"),
  183.   _("Cleaning up"),
  184. )
  185.  
  186. class DistUpgradeView(object):
  187.     " abstraction for the upgrade view "
  188.     def __init__(self):
  189.         pass
  190.     def getOpCacheProgress(self):
  191.         " return a OpProgress() subclass for the given graphic"
  192.         return apt.progress.OpProgress()
  193.     def getFetchProgress(self):
  194.         " return a fetch progress object "
  195.         return FetchProgress()
  196.     def getInstallProgress(self, cache=None):
  197.         " return a install progress object "
  198.         return InstallProgress()
  199.     def getTerminal(self):
  200.         return DumbTerminal()
  201.     def updateStatus(self, msg):
  202.         """ update the current status of the distUpgrade based
  203.             on the current view
  204.         """
  205.         pass
  206.     def abort(self):
  207.         """ provide a visual feedback that the upgrade was aborted """
  208.         pass
  209.     def setStep(self, step):
  210.         """ we have 6 steps current for a upgrade:
  211.         1. Analyzing the system
  212.         2. Updating repository information
  213.         3. fetch packages
  214.         3. Performing the upgrade
  215.         4. Post upgrade stuff
  216.         5. Complete
  217.         """
  218.         pass
  219.     def hideStep(self, step):
  220.         " hide a certain step from the GUI "
  221.         pass
  222.     def showStep(self, step):
  223.         " show a certain step from the GUI "
  224.         pass
  225.     def confirmChanges(self, summary, changes, downloadSize,
  226.                        actions=None, removal_bold=True):
  227.         """ display the list of changed packages (apt.Package) and
  228.             return if the user confirms them
  229.         """
  230.         self.confirmChangesMessage = ""
  231.         self.toInstall = []
  232.         self.toUpgrade = []
  233.         self.toRemove = []
  234.         self.toDowngrade = []
  235.         for pkg in changes:
  236.             if pkg.markedInstall: self.toInstall.append(pkg.name)
  237.             elif pkg.markedUpgrade: self.toUpgrade.append(pkg.name)
  238.             elif pkg.markedDelete: self.toRemove.append(pkg.name)
  239.             elif pkg.markedDowngrade: self.toDowngrade.append(pkg.name)
  240.         # sort it
  241.         self.toInstall.sort()
  242.         self.toUpgrade.sort()
  243.         self.toRemove.sort()
  244.         self.toDowngrade.sort()
  245.         # no re-installs 
  246.         assert(len(self.toInstall)+len(self.toUpgrade)+len(self.toRemove)+len(self.toDowngrade) == len(changes))
  247.         # now build the message (the same for all frontends)
  248.         msg = "\n"
  249.         pkgs_remove = len(self.toRemove)
  250.         pkgs_inst = len(self.toInstall)
  251.         pkgs_upgrade = len(self.toUpgrade)
  252.         # FIXME: show detailed packages
  253.         if pkgs_remove > 0:
  254.           # FIXME: make those two separate lines to make it clear
  255.           #        that the "%" applies to the result of ngettext
  256.           msg += ngettext("%d package is going to be removed.",
  257.                           "%d packages are going to be removed.",
  258.                           pkgs_remove) % pkgs_remove
  259.           msg += " "
  260.         if pkgs_inst > 0:
  261.           msg += ngettext("%d new package is going to be "
  262.                           "installed.",
  263.                           "%d new packages are going to be "
  264.                           "installed.",pkgs_inst) % pkgs_inst
  265.           msg += " "
  266.         if pkgs_upgrade > 0:
  267.           msg += ngettext("%d package is going to be upgraded.",
  268.                           "%d packages are going to be upgraded.",
  269.                           pkgs_upgrade) % pkgs_upgrade
  270.           msg +=" "
  271.         if downloadSize > 0:
  272.           msg += _("\n\nYou have to download a total of %s. ") %\
  273.               apt_pkg.SizeToStr(downloadSize)
  274.           msg += self.getFetchProgress().estimatedDownloadTime(downloadSize)
  275.         if (pkgs_upgrade + pkgs_inst + pkgs_remove) > 100:
  276.           msg += "\n\n%s" % _( "Fetching and installing the upgrade "
  277.                                "can take several hours. Once the download "
  278.                                "has finished, the process cannot be cancelled.")
  279.         # Show an error if no actions are planned
  280.         if (pkgs_upgrade + pkgs_inst + pkgs_remove) < 1:
  281.           # FIXME: this should go into DistUpgradeController
  282.           summary = _("Your system is up-to-date")
  283.           msg = _("There are no upgrades available for your system. "
  284.                   "The upgrade will now be canceled.")
  285.           self.error(summary, msg)
  286.           return False
  287.         # set the message
  288.         self.confirmChangesMessage = msg
  289.         return True
  290.  
  291.     def askYesNoQuestion(self, summary, msg, default='No'):
  292.         " ask a Yes/No question and return True on 'Yes' "
  293.         pass
  294.     def confirmRestart(self):
  295.         " generic ask about the restart, can be overridden "
  296.         summary = _("Reboot required")
  297.         msg =  _("The upgrade is finished and "
  298.                  "a reboot is required. "
  299.                  "Do you want to do this "
  300.                  "now?")
  301.         return self.askYesNoQuestion(summary, msg)
  302.     def error(self, summary, msg, extended_msg=None):
  303.         " display a error "
  304.         pass
  305.     def information(self, summary, msg, extended_msg=None):
  306.         " display a information msg"
  307.         pass
  308.     def processEvents(self):
  309.         """ process gui events (to keep the gui alive during a long
  310.             computation """
  311.         pass
  312.     def showDemotions(self, summary, msg, demotions):
  313.       """
  314.       show demoted packages to the user, default implementation
  315.       is to just show a information dialog
  316.       """
  317.       self.information(summary, msg, "\n".join(demotions))
  318.  
  319. if __name__ == "__main__":
  320.   fp = FetchProgress()
  321.   fp.pulse()
  322.